package name.cyrax.red.jpv.impl.funcs.core;

import name.cyrax.red.jpv.api.adapters.DoubleAdapter;
import name.cyrax.red.jpv.api.adapters.IntegerAdapter;
import name.cyrax.red.jpv.api.adapters.LongAdapter;
import name.cyrax.red.jpv.api.adapters.NumericAccessor;
import name.cyrax.red.jpv.api.adapters.NumericAdapter;
import name.cyrax.red.jpv.api.funcs.Functor;
import name.cyrax.red.jpv.api.funcs.FunctorImplementor;
import name.cyrax.red.jpv.api.funcs.FunctorInstance;
import name.cyrax.red.jpv.api.funcs.InvocableAdapter;
import name.cyrax.red.jpv.api.funcs.InvocationContext;
import name.cyrax.red.jpv.api.funcs.info.FunctorMethod;
import name.cyrax.red.jpv.api.funcs.info.ValueType;
import name.cyrax.red.jpv.impl.adapters.NumericImpl;

public abstract class FunctorImpl<R extends NumericAdapter, T> implements Functor
{
	private final NumericAccessor<T> IMPL_GETTER = new NumericAccessor<T>() {

		@Override public T asInteger(IntegerAdapter adapter)
		{
			return getPrimaryFunctorImplementor().newAdapter(adapter);
		}

		@Override public T asLong(LongAdapter adapter)
		{
			return getPrimaryFunctorImplementor().newAdapter(adapter);
		}

		@Override public T asDouble(DoubleAdapter adapter)
		{
			return getPrimaryFunctorImplementor().newAdapter(adapter);
		}  
	};
	
	protected T newImplementation(InvocationContext context, InvocableAdapter p0) {
		return p0.access(context, IMPL_GETTER);
	}
	
	protected abstract FunctorImplementor<T> getPrimaryFunctorImplementor();
	
	protected FunctorInstance getFunctorInstance(final FunctorMethod functorMethod) {
		return new AbstractFunctorInstance(this, functorMethod, this);
	}
	
	protected final void checkResultType(FunctorMethod functorMethod, ValueType expectedType)
	{
		if (expectedType == ValueType.UNKNOWN) return;
		
		ValueType rt = functorMethod.result();
		if (rt != expectedType) {
			throw new IllegalArgumentException(rt + " can not be used as " + expectedType);
		}
	}

	protected final <N extends NumericImpl<?>> N checkAdapterType(N expectedType)
	{
		FunctorMethod functorMethod = expectedType.getFunctorInstance().getMethodInfo();
		checkResultType(functorMethod, expectedType.getUnderlyingType());
		return expectedType;
	}
}